EF Core met MySQL en ASP.NET Core MySQL
I.p.v. de gegevens letterlijk in de HTML pagina te zetten, gaan we nu een database maken met daarin een tabel om deze gegevens daarin te bewaren.
Het Entity Framework is een Object-Relational mapping-technologie, waarmee gegevens uit een relationele database kunnen worden omgezet in objecten met gegevens die door objectgeörienteerde programmeertalen gebruikt kunnen worden.
Een belangrijke functie binnen het framework is het automatiseren van taken. Ontwikkelaars moeten niet meer zelf in hun code de conversieslag tussen database en applicatie maken. Het Entity Framework zorgt ervoor dat data op een eenduidige wijze aan de ontwikkelaar ter beschikking wordt gesteld zodat er op een abstractere manier mee gewerkt kan worden.
Bronnen
- Learn ADO.NET by building CRUD features in ASP.NET Core Application, August 19, 2020
- .NET Core, Tips and Guidelines, Understanding EFCore Scaffold-DbContext Commands in .NET Core, March 08, 2020
Doelstelling
- We leren hoe je vanuit een Razor Page verbinding maakt met een MySQL database door gebruik te maken het Entity Framework
Stappenplan
- Video
- Wat hieraan voorafgaat
- Je hebt de tabellen van de vorige les gemaakt en gegevens geïnsert.
- Hier volgt de DDL voor alle tabellen
- Je kan eventueel de tabellen op basis van mijn tabellen creëren. Jullie hebben allemaal SELECT toegang tot de database Docent2:
use Student?; -- gebruik je eigen database SET FOREIGN_KEY_CHECKS = 0; DROP TABLE if exists `OrderItem`; CREATE TABLE `OrderItem` SELECT * FROM Docent2.`OrderItem`; DROP TABLE if exists `Order`; CREATE TABLE `Order` SELECT * FROM Docent2.`Order`; DROP TABLE if exists `ShippingMethod`; CREATE TABLE `ShippingMethod` SELECT * FROM Docent2.`ShippingMethod`; DROP TABLE if exists `OrderStatus`; CREATE TABLE `OrderStatus` SELECT * FROM Docent2.`OrderStatus`; DROP TABLE if exists `Customer`; CREATE TABLE `Customer` SELECT * FROM Docent2.`Customer`; DROP TABLE if exists `Book`; CREATE TABLE `Book` SELECT * FROM Docent2.`Book`; SET FOREIGN_KEY_CHECKS = 1;
- Het BiblioAdmin project
- Maak een nieuw Razor Pages project met de naam BiblioAdmin in de map Programmeren3
-
Kies voor een ASP.NET Core Web Application:
-
Configureer je project:
-
Configureer de ASP.NET Core Web Application:
-
MySql Client Provider
Er bestaan verschillende data providers voor MySQL. Het is even zoeken op het internet om de juiste te vinden. - Entity Framework
Het Entity Framework is een Object-Relational mapping-technologie, waarmee gegevens uit een relationele database kunnen worden omgezet in objecten met gegevens die door objectgeörienteerde programmeertalen gebruikt kunnen worden.
Een belangrijke functie binnen het framework is het automatiseren van taken. Ontwikkelaars moeten niet meer zelf in hun code de conversieslag tussen database en applicatie maken. Het Entity Framework zorgt ervoor dat data op een eenduidige wijze aan de ontwikkelaar ter beschikking wordt gesteld zodat er op een abstractere manier mee gewerkt kan worden.
Voordat je met EF kan beginnen, moet je de EF assemblies lokaal installeren. Microsoft's nieuwe distributiemodel is niet meer gebaseerd op de ouderwetse Windows installers, maar op nieuwe technologieën zoals NuGet en Git.
- Omdat de Pomelo MySQL driver nog niet werkt met de laatste EF versie microsoft moeten we die downgraden van 5.0.0 naar 3.1.10:
Install-Package Microsoft.EntityFrameworkCore -Version 3.1.10
- Het MySQL Entity Framework voor .NET Core installeren. De laatste versie vind je op de NuGet pagina:
Install-Package MySql.Data.EntityFrameworkCore -Version 8.0.22
-
EFCore Package Manager Console Tool kan je installeren met behulp van het volgende Nuget pakket. Typ in de projectmap:
Install-Package Microsoft.EntityFrameworkCore.Tools -Version 3.1.10
We moeten expliciet versie 3.1.10 opgeven omdat Pomelo nog niet compatibel is met de laatste versie van EF Core 5.0.0.
- We hebben ook de Pomelo versie nodig om het model te kunnen genereren vanaf de bestaande database. De laatste versie vind je op de NuGet pagina:
Install-Package Pomelo.EntityFrameworkCore.MySql
- Omdat de Pomelo MySQL driver nog niet werkt met de laatste EF versie microsoft moeten we die downgraden van 5.0.0 naar 3.1.10:
- We kunnen nu het model laten genereren. Wij gebruiken Bll klassen als model.
- We gaan de modelklassen in een map met de naam Bll/ plaatsen. Vandaar dat we de optie
-OutputDir Bll/
toevoegen. - We beginnen met de fluent API:
- in de Package Manager Console typ je:
Scaffold-DbContext "server=51.38.37.150;user id=Docent1;password=Docent_XXXXXXXXXXX;port=3306;database=Docent2;SslMode=none" Pomelo.EntityFrameworkCore.MySql -OutputDir Bll/
- als er een $ teken in je paswoord staat gebruik dan enkelvoudige aanhalingstekens:
Scaffold-DbContext 'server=51.38.37.150;user id=Student6;password=Student$123456789;port=3306;database=Student6;SslMode=none' Pomelo.EntityFrameworkCore.MySql -OutputDir Bll/ -Force
- als je de database wijzigt kan je de Model/Bll-klassen opnieuw genereren en de oude laten overschrijven door de
-Force
optie toe te voegen:Scaffold-DbContext "server=51.38.37.150;user id=Docent1;password=Docent_12345678;port=3306;database=Docent2;SslMode=none" Pomelo.EntityFrameworkCore.MySql -OutputDir Bll/ -Force
- Voor elke tabel in de database werd een Bll/Model klasse gegenereerd:
- in de Package Manager Console typ je:
- Zo ziet een Bll/Model klasse eruit:
using System; using System.Collections.Generic; #nullable disable namespace BiblioAdmin.Bll { public partial class Book { public string ImageUrl { get; set; } public string Author { get; set; } public string Title { get; set; } public string Subtitle { get; set; } public string PublicationDate { get; set; } public string Publisher { get; set; } public string Category { get; set; } public string Size { get; set; } public string NumberOfPages { get; set; } public decimal? Price { get; set; } public string Language { get; set; } public string ProductCode { get; set; } public string ProductType { get; set; } public string ProductTypeFull { get; set; } public string Description { get; set; } public int Id { get; set; } } }
Let op het gebruik van#nullable disable
. Dat is nieuw in C# 8.0. Info hierover op Nullable reference types.
Verder staat er eendecimal?
. Dit is een Nullable value type. Info hierover op Nullable value types.
- We gaan de modelklassen in een map met de naam Bll/ plaatsen. Vandaar dat we de optie
- Om de Bll/Model klassen voor t bereiden voor de Views, kunnen we naast de fluent API ook Data Annotations gebruiken. We leren eerst wat Data Annotations zijn en daarna gaan we data geannoteerde bll/modelklassen genereren.
- Validatie toevoegen aan de model klassen:
- Om data-geannoteerde bll/modelklassen te genereren:
Scaffold-DbContext "server=164.132.231.13;user id=Docent1;password=AqDHX9q2Z;port=3306;database=docent300;SslMode=none" Pomelo.EntityFrameworkCore.MySql -OutputDir Bll -Force -DataAnnotations
- Er zijn twee soorten data-annotaties toegevoegd:
- Eén die gebruikt kan worden om eventueel de tabel opnieuw aan te maken:
[Column(TypeName = "varchar(255)")]
- En één die als validatie kan gebruikt worden en ook om eventueel de tabel in MySQL opnieuw te maken:
[Required]
- Eén die gebruikt kan worden om eventueel de tabel opnieuw aan te maken:
- De volledige geannoteerde bll/modelklasse:
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; #nullable disable namespace BiblioAdmin.Bll { [Table("Book")] public partial class Book { [Column("ImageURL", TypeName = "varchar(255)")] public string ImageUrl { get; set; } [Required] [Column(TypeName = "varchar(255)")] public string Author { get; set; } [Required] [Column(TypeName = "varchar(255)")] public string Title { get; set; } [Column(TypeName = "varchar(255)")] public string Subtitle { get; set; } [Column(TypeName = "varchar(20)")] public string PublicationDate { get; set; } [Column(TypeName = "varchar(120)")] public string Publisher { get; set; } [Column(TypeName = "varchar(255)")] public string Category { get; set; } [Column(TypeName = "varchar(20)")] public string Size { get; set; } [Column(TypeName = "varchar(6)")] public string NumberOfPages { get; set; } public decimal? Price { get; set; } [Column(TypeName = "varchar(20)")] public string Language { get; set; } [Column(TypeName = "varchar(50)")] public string ProductCode { get; set; } [Column(TypeName = "varchar(50)")] public string ProductType { get; set; } [Column(TypeName = "varchar(80)")] public string ProductTypeFull { get; set; } [Column(TypeName = "varchar(2000)")] public string Description { get; set; } [Column(TypeName = "int(11)")] public int Id { get; set; } } }
-
DBContext
-
Video
-
Tussen de Bll/Modelklassen staat ook de DAL klasse, de klasse die door EF gebruikt wordt om de verbinding met de database te maken. De naam van het bestand is
Docent2Context.cs
. Deze naam bestaat uit de databasenaam gevolgd doorContext
. Bij jullie zal datStudent
, gevolgd door 1, 2 of een ander getal enContext
: -
De kern van het Entity Framework Core is de
Microsoft.EntityFrameworkCore.DbContext
klasse. Het is deze klasse (of, beter gezegd, de klassen die jij maakt op basis van deze klasse) die de toegangspoort tot de database is en alle methoden biedt die je nodig hebt om met de database te werken. -
Voordat een klassemodel kan worden gebruikt om een query op een database uit te voeren, moet Entity Framework weten hoe het code (klassen, eigenschappen, en instanties) heen en weer moeten vertalen van C# naar SQL (in het bijzonder, tabellen, kolommen en rijen ). Daarvoor gebruikt het ORM of object relational mapping. Een context is een klasse die erft van
DBContext
en die een aantal entiteit-collecties toegankelijk maakt in de vorm vanDbSet<T>
eigenschappen. - De
Docent2Context
klasse Deze klasse bevat getters en setters voor elke tabel (entiteit). De rijen va een tabel worden opgeslagen in een generieke lijst. Voor elke tabel wordt er een generieke lijst gedeclareerd. Het type is de Bll/modelklasse van de entiteit:using Microsoft.EntityFrameworkCore; #nullable disable namespace BiblioAdmin.Bll { public partial class Docent2Context : DbContext { public Docent2Context() { } public Docent2Context(DbContextOptions<Docent2Context> options) : base(options) { } public virtual DbSet<Book> Books { get; set; } public virtual DbSet<Customer> Customers { get; set; } public virtual DbSet<Order> Orders { get; set; } public virtual DbSet<OrderItem> OrderItems { get; set; } public virtual DbSet<OrderStatus> OrderStatuses { get; set; } public virtual DbSet<ShippingMethod> ShippingMethods { get; set; } } }
-
In een vorige les hebben we gezien hoe we met behulp van Dependency Injection een DAL klasse als een service in onze app ter beschikking kunnen stellen. We doen dat omdat we dan heel gemakkelijk van DAL kunnen veranderen en bijvoorbeeld de DAL voor CSV bestanden kunnen injecteren. We hoeven daarvoor alleen de service te wijzigen en de rest van de code blijft ongewijzigd!
We gaan dat hier doen voor de SQL provider. We gebruiken hier een MySQL provider maar als we die met DI ter beschikking stellen kunnen we gemakkelijk overgaan naar een andere provider, bv. voor MsSql.Vermits we een Razor Pages project gecreëerd hebben is Depencency Injection al voor ons geïnstalleerd door Visual Studio. Dat hoeven we dus zelf niet meer te doen. We moeten alleen de service toevoegen in
ConfigureServices
methode van het Startup.cs bestand.-
We registreren eerst de DAL klasse zelf, in EF heet die
DbContext
:public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddDbContext<Bll.Docent2Context>(options => options.UseMySQL(Configuration.GetConnectionString("Biblio"))); }
- voeg de
Biblio
connectiestring toe on appsettings.json:{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "ConnectionStrings": { "Biblio": "server=51.38.37.150;user id=Docent2;password=Docent_XXXXXXXXX;port=3306;database=Docent2;SslMode=none" } }
Vervang de naam van de database, de gebruikernaam en het paswoord door je eigen gegevens. - En verwijder de connectiestring uit de
DBContext
klasse:protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (!optionsBuilder.IsConfigured) {
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. See http://go.microsoft.com/fwlink/?LinkId=723263 for guidance on storing connection strings. optionsBuilder.UseMySql("server=51.38.37.150;user id=Docent2;password=Docent_63U1T2R3;port=3306;database=Docent2;sslmode=none", x => x.ServerVersion("5.7.32-mysql"));} }
-
-
-
Een Razor Page maken
-
Maak een submap in Pages met de naam OrderStatus.
-
Maak daarin een Razor Page met de naam Index.cshtml.
-
Gebruik constructor-injectie zodat de deze 'geïnjecteerde'
DbContext
klasse gebruikt kan worden in de klasse BiblioAdmin.Pages.OrderStatus.IndexModel. Die klasse staat in het code behind bestand Pages/OrderStatus/Index.cshtml.cs bestand:using Microsoft.AspNetCore.Mvc.RazorPages; namespace BiblioAdmin.Pages.OrderStatus { public class IndexModel : PageModel { private readonly Bll.Docent2Context dbContext; // voeg constructor toe om geïnjecteerde DBContext // te kunnen binnenkrijgen in deze klasse public IndexModel(Bll.Docent2Context dbContext) { this.dbContext = dbContext; } public void OnGet() { } } }
- In de
OnGet
methode moeten we de rijen uit de tabelOrderStatus
ophalen. We maken een public property, een generieke lijst waarin we instanties van deBll.OrderStatus
klasse kunnen stoppen en we gebruiken deToList
methode van deDbContext
van EF om de rijen uit de tabel op te halen. We geven ook op dat we twee namespaces zullen gebruiken:using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; namespace BiblioAdmin.Pages.OrderStatus { public class IndexModel : PageModel { private readonly Bll.Docent2Context dbContext; public List<Bll.OrderStatus> OrderStatusList { get; set; } // voeg constructor toe om geïnjecteerde DBContext // te kunnen binnenkrijgen in deze klasse public IndexModel(Bll.Docent2Context dbContext) { this.dbContext = dbContext; } public void OnGet() { OrderStatusList = dbContext.OrderStatus.ToList(); } } }
- We willen dat uitproberen en passen de Pages/Shared/_Layout.cshtml.cshtml pagina aan. We wijzigen de derde link zodat die naar de Pages/Biblio/Index.cshtml pagina springt als je erop klikt:
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse"> <ul class="navbar-nav flex-grow-1"> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-page="/OrderStatus/Index">Order Status Index</a> </li> </ul> </div>
- Tenslotte passen we Pages/OrderStatus/Index.cshtml view aan:
@page @model BiblioAdmin.Pages.Biblio.IndexModel @{ } <h1>Order Status</h1> <table class="list"> <thead> <tr> <th> Id </th> <th> Naam </th> <th> Beschrijving </th> </tr> </thead> <tbody> @foreach (var item in Model.OrderStatusList) { <tr> <td> @item.Id </td> <td> @item.Name </td> <td> @item.Description </td> </tr> } </tbody> </table>
-
- De OrderStatus/ReadingOne.cshtml Razor Page maken
- Video
- Wireframe
- In de map Pages/OrderStatus voeg je een gewone Razor Page toe met de naam ReadingOne.cshtml.
- In de OrderStatus/Index.cshtml voeg je een kolom toe met een link naar deze ReadingOne pagina. Als laatste parameter geef je de
Id
mee van de rij die je wilt ophalen;@foreach (var item in Model.OrderStatusList) { <tr> <td> <a href="OrderStatus/ReadingOne/@item.Id">Select</a> | </td> <td> @item.Id </td> <td> @item.Name </td> <td> @item.Description </td> </tr> }
- De
ReadingOneModel
klasse aanpassen om een connectie met de database te kunnen maken
Gebruik constructor-injectie zodat de deze 'geïnjecteerde'DbContext
klasse gebruikt kan worden in de klasse BiblioAdmin.Pages.OrderStatus.ReadingOneModel. Die klasse staat in het code behind bestand Pages/OrderStatus/ReadingOne.cshtml.cs bestand:using Microsoft.AspNetCore.Mvc.RazorPages; namespace BiblioAdmin.Pages.OrderStatus { public class ReadingOneModel : PageModel { private readonly Bll.Docent2Context dbContext; // voeg constructor toe om geïnjecteerde DBContext // te kunnen binnenkrijgen in deze klasse public ReadingOneModel(Bll.Docent2Context dbContext) { this.dbContext = dbContext; } public void OnGet() { } } }
- En we voegen er twee properties aan toe, een object van de klasse
OrderStatus
en een generieke lijst vanOrderStatus
objecten:public Bll.OrderStatus OrderStatus { get; set; } public List<Bll.OrderStatus> OrderStatusList { get; set; }
- In de OnGet methode haal met behulp van dbContext de rij uit tabel op. Let erop dat deze methode nu een parameter heeft, nl. int? id. Het vraagteken geeft aan dat die optioneel is. We lezen ook alle rijen in uit de tabel:
public void OnGet(int? id) { this.OrderStatus = dbContext.OrderStatus.SingleOrDefault(m => m.Id == id); OrderStatusList = dbContext.OrderStatus.ToList(); }
- In de view, de ReadingOne.cshtml pagina moeten we die id declareren in de @Page directive:
@page "{id}"
- Tenslotte voegen de rest van de HTML toe en we volgen de wireframe. De volledite Razor Page ziet er dat zo uit:
@page "{id}" @model BiblioAdmin.Pages.OrderStatus.ReadingOneModel @{ } <h1>OrderStatus ReadingOne</h1> <nav class="control-panel"> <a href="/Index"> <span>Hier komt het logo</span> </a> <h1 class="banner">Biblio Admin</h1> </nav> <div class="show-room entity"> <div class="detail"> <div class="command-panel"> <h2>Order Status</h2> <a href="/OrderStatus/UpdatingOne/@Model.OrderStatus.Id" class="tile"> <span class="icon-pencil"></span> <span class="screen-reader-text">Updating One</span> </a> <a href="/OrderStatus/InsertingOne" class="tile"> <span class="icon-plus"></span> <span class="screen-reader-text">Inserting One</span> </a> <a href="/OrderStatus/DeleteOne/@Model.OrderStatus.Id" class="tile"> <span class="icon-remove"></span> <span class="screen-reader-text">Delete One</span> </a> <a href="/OrderStatus/Cancel" class="tile"> <span class="icon-close"></span> <span class="screen-reader-text">Annuleren</span> </a> </div> <fieldset> <div> <label for="OrderStatus-Name">Id</label> <input id="OrderStatus-Name" name="OrderStatus-Name" type="text" value="@Model.OrderStatus.Id" readonly> </div> <div> <label for="OrderStatus-Name">Naam</label> <input id="OrderStatus-Name" name="OrderStatus-Name" type="text" value="@Model.OrderStatus.Name" readonly> </div> <div> <label for="OrderStatus-Name">Beschrijving</label> <textarea id="OrderStatus-Name" name="OrderStatus-Name" type="text" readonly>@Model.OrderStatus.Description</textarea> </div> </fieldset> <div class="feedback"></div> </div> <aside class="list"> <table class="list"> <thead> <tr> <th> </th> <th> Id </th> <th> Naam </th> </tr> </thead> <tbody> @foreach (var item in Model.OrderStatusList) { <tr> <td> <a href="OrderStatus/ReadingOne/@item.Id">-></a> | </td> <td> @item.Id </td> <td> @item.Name </td> </tr> } </tbody> </table> </aside> </div>
- De volledige code van
OrderStatusModel
klasse:using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; namespace BiblioAdmin.Pages.OrderStatus { public class ReadingOneModel : PageModel { private readonly Bll.Docent2Context dbContext; // voeg constructor toe om geïnjecteerde DBContext // te kunnen binnenkrijgen in deze klasse public ReadingOneModel(Bll.Docent2Context dbContext) { this.dbContext = dbContext; } public Bll.OrderStatus OrderStatus { get; set; } public List<Bll.OrderStatus> OrderStatusList { get; set; } public void OnGet(int? id) { this.OrderStatus = dbContext.OrderStatus.SingleOrDefault(m => m.Id == id); OrderStatusList = dbContext.OrderStatus.ToList(); } } }
- En dat is het resultaat: We volgen de wireframe maar hebben nog geen opmaak met CSS toegevoegd.
- Video
- Gegevens opvragen via de
DbSet (LINQ)
Het opvragen van gegevens in Entity Framework Core wordt uitgevoerd op basis van de DbSet-eigenschappen van de DbContext. De DbSet vertegenwoordigt een verzameling entiteiten van een specifiek type - het type dat wordt gespecificeerd door het parameter type.
Query's worden gespecificeerd met behulp van Language Integrated Query (LINQ), een onderdeel in het .NET Framework dat queryfuncties biedt voor verzamelingen in C #.
LINQ-query's kunnen worden geschreven met behulp van de querysyntaxis of de methodesyntaxis. Query-syntaxis lijkt op SQL. De EF Core-provider die je gebruikt, is verantwoordelijk voor het vertalen van de LINQ-query naar de daadwerkelijke SQL die moet worden uitgevoerd op de database. Meer info op Querying data via the DbSet.